package HostChecker::Utils;

# $Id: Utils.pm 225 2010-07-12 19:00:41Z dolbanavt $
# $Date: 2010-07-12 22:00:52 +0300 (Пнд, 12 Июл 2010) $
# $HeadURL: https://robosearch.googlecode.com/svn/branches/reborn/lib/HostChecker/Utils.pm $
# $LastChangedRevision: 348 $

use JIP::ToolSet;
use Carp qw( croak );
use Const::Fast qw( const );
use English qw( -no_match_vars );
use IPC::Open3::Utils qw( put_cmd_in );
use JIP::Utils qw( check_named_params );
use Exporter::Lite;
use Net::NBName;
use Net::Ping;
use threads;
use threads::shared;

our $VERSION = '1.0';

our @EXPORT_OK = qw(
    find_active_hosts_in
    resolve_nbn_for
    find_active_ftp_servers
);

const my $PING_TIMEOUT     => 1;
const my $FTP_DEFAULT_PORT => 21;

# configures behavior and functionality of put_cmd_in(). boolean arguments
const my $ARGS_FOR_OPEN3 => {
    close_stdin            => 1,
    carp_open3_errors      => 1,
    stop_read_on_open3_err => 1,
};

my @all_servers    : shared;
my @servers_online : shared;

sub find_active_hosts_in {
    my $subnet_mask = shift;

    croak 'subnet_mask required' if not defined $subnet_mask;

    my $nmap_command = join q{ }, (
        'nmap',
        '-n',
        '-sT',
        '-p21',
        $subnet_mask,
    );

    put_cmd_in( $nmap_command, \my $stdout, \my $stderr, $ARGS_FOR_OPEN3 );

    if ( $stderr ) {
        chomp $stderr;
        croak 'ошибка выполнения команды `'. $nmap_command .'`: '. $stderr;
    }

    croak 'nmap вернул пустой ответ' if not defined $stdout;

    my ( @servers ) = $stdout =~ m{Interesting\sports\son\s
                                   (\d+\.\d+\.\d+\.\d+):
                                  }msxg;

    return @servers;
}

sub resolve_nbn_for {
    my $ip_address = shift;

    croak 'ip_address required' if not defined $ip_address;

    my $node_status = Net::NBName->new()->node_status( $ip_address );

    if ( $node_status ) {
        for my $each_record ( $node_status->names ) {
            if ( $each_record->suffix == 0 and $each_record->G eq 'UNIQUE' ) {
                return $each_record->name
                    if $each_record->name !~ m{^IS~}xms and
                       $each_record->name !~ m{[^[:ascii:]]}xms;
            }
        }
    }

    return;
}

sub find_active_ftp_servers {
    my %param = @ARG;

    check_named_params(
        rules_ref  => {
            max_threads => { required => 1, defined  => 1 },
            hosts_ref   => { required => 1, defined  => 1 },
        },
        params_ref => \%param,
    );

    @all_servers = @{ $param{'hosts_ref'} };

    # fire and forget
    threads->create( \&_worker ) for 1 .. $param{'max_threads'};

    # waiting for a thread to exit
    for my $each_thread ( threads->list() ){ $each_thread->join() }

    return @servers_online;
}

sub _worker {
    my $ping = Net::Ping->new('syn');

    $ping->port_number( $FTP_DEFAULT_PORT );

    while ('true') {
        my $ip_address;

        # блокируется @all_servers
        # блокировка действует только внутри блока
        # блок максимально короткий и минималистичный
        {
            lock @all_servers;

            threads->exit() unless @all_servers;

            $ip_address = shift @all_servers;
        }

        $ping->ping( $ip_address, $PING_TIMEOUT );

        if ( $ping->ack() ) {
            lock @servers_online;

            push @servers_online, $ip_address;
        }
    }

    return 1;
}

1;

__END__

=head1 NAME

HostChecker::Utils - набор утилит используемых хостчекером

=head1 SYNOPSIS

    use HostChecker::Utils
        find_active_hosts_in
        resolve_nbn_for
        find_active_ftp_servers
    );

    # ищет активные хосты в сети
    my @hosts = find_active_hosts_in('81.89.188.0/23');

    # рехолвит нетбиос-имя хоста по его адресу
    my $netbios_name = resolve_nbn_for('192.168.1.1');

    # в списке адресов резолвит действующие FTP-серверы
    my @hosts = find_active_ftp_servers(
        max_threads => 100500,
        hosts_ref   => \@hosts,
    );

=head1 DEPENDENCIES

Exporter::Lite (from CPAN)

Const::Fast (from CPAN)


IPC::Open3::Utils (from CPAN)

Net::NBName (from CPAN)

Net::Ping (from CPAN)

JIP::ToolSet (from base_lib)

JIP::Utils (from base_lib)

=head1 AUTHOR

Vladimir Zhavoronkov, <dolbanavt (at) Google Mail>

http://dolbanavt.livejournal.com/

=head1 LICENSE AND COPYRIGHT

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

=cut
